Wizard: proactively surface stale pre-MAL-57 VHID daemon plist with one-click repair#904
Conversation
Code Review — PR #904Overall this is a well-scoped, well-reasoned PR. The MAL-57 migration path is carefully gated (severity, cache placement, double-alarm suppression) and the stale-helper guard closes a real loop that would otherwise repeat forever. A few findings below. Bug:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ca4c472c53
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| AppLogger.shared.warn( | ||
| "⚠️ [PrivilegedOperationsRouter] VHID daemon plist still misconfigured after helper install — stale helper template, retrying via in-app install" | ||
| ) | ||
| try await sudoInstallRequiredRuntimeServices() |
There was a problem hiding this comment.
Bypass the stale helper for the retry
When this guard detects a resident pre-MAL-57 helper, sudoInstallRequiredRuntimeServices() still does not force the in-app plist generator: it calls ServiceBootstrapper.installAllServices(), whose first step is InstallerEngine().runSingleAction(.repairVHIDDaemonServices, using: PrivilegeBroker()), and that routes back through PrivilegedOperationsRouter.repairVHIDDaemonServices() to the same responsive helper. In the exact stale-helper scenario described here, the retry can therefore rewrite the old plist again and return success, leaving ProcessType=Interactive missing and the repair loop unresolved until the helper is restarted or bypassed explicitly.
Useful? React with 👍 / 👎.
ca4c472 to
04de9f5
Compare
|
Review addressed (picked up from a parallel session since this PR had stalled at BEHIND):
Rebased onto master (now includes #897/#898/#902). |
04de9f5 to
db59240
Compare
|
Review findings addressed in the updated commit:
|
Code Review — PR #904: Proactive MAL-57 plist migrationOverall this is a well-thought-out change that fills a real gap (existing installs that never hit a repair never migrated). The design decisions are sound and the test coverage is thorough. A few things worth examining before merge: What's working wellDRY extraction of
Stale-helper guard design ( Severity Presence-gating in
Concerns1. Double filesystem op for corrupt plists ( guard let dict = NSDictionary(contentsOfFile: plistPath) as? [String: Any] else {
return FileManager.default.fileExists(atPath: plistPath)
}When the plist exists but is corrupt, guard let dict = NSDictionary(contentsOfFile: plistPath) as? [String: Any] else {
// Plist missing → false (owned by service health); present but unparseable → true (repair rewrites it)
return FileManager.default.fileExists(atPath: plistPath)
}The behavior is correct as written; this is just a minor readability/efficiency note. 2. Component cache may stay stale for up to 15s after the stale-helper fallback ( After 3. Incidental Several 4. No router-level test for the stale-helper guard path The Minor notes
Bottom line: The design is solid and the test coverage is good. Items 2 (cache staleness after stale-helper fallback) and 3 (incidental reformats) are the ones worth double-checking before merge. Items 1 and 4 are informational. |
Code ReviewOverviewThis PR wires the pre-MAL-57 VHID daemon plist validation into the wizard's health model so existing installs with a stale The change is well-scoped and the reasoning is sound. A few things worth flagging: Code QualitySolid decisions:
Potential Issue:
|
db59240 to
967b6af
Compare
…ne-click repair
isVHIDDaemonConfiguredCorrectly() (ProgramArguments path + ProcessType=
Interactive, the MAL-57 starvation fix) was only consumed by the repair
postflight, where it trivially passes because repair just rewrote the
plist. Old installs with a pre-fix plist never migrated unless repair
ran for some other reason.
Wire plist-content validation into the wizard's health model:
- ServiceHealthChecker.isVHIDDaemonPlistPresentButMisconfigured():
presence-gated content check — a missing plist stays attributed to
service health ("not installed"), only an existing-but-stale plist
counts as misconfigured
- ComponentStatus.vhidDaemonPlistMisconfigured (defaulted init param,
existing construction sites unchanged), populated by
SystemValidator.checkComponents()
- SystemInspector emits the previously dormant
.component(.vhidDaemonMisconfigured) issue (category .installation,
autoFix .installRequiredRuntimeServices) — already consumed by
KarabinerComponentsStatusEvaluator, WizardRouter, and the Karabiner
components page repair flow
- ActionDeterminer includes .installRequiredRuntimeServices in repair/
install plans when the plist is stale even if the daemon is healthy
Health semantics (vhidDaemonServiceHealthy, test-env plist-existence
path) are untouched; state stays .active while the issue drives routing
per the issue-driven WizardRouter, honoring ADR-026 ordering.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
967b6af to
7bf7f82
Compare
PR Review — Wizard: proactively surface stale pre-MAL-57 VHID daemon plistOverall this is a well-structured PR. The motivation is clear, the severity choice is correct, the double-alarm prevention is thoughtful, and Bug:
|
Code ReviewOverviewThis PR closes a real migration gap: the MAL-57 starvation fix (adding Overall this is well-structured work. A few items worth discussing: Finding 1 — File: return Foundation.FileManager().fileExists(atPath: plistPath)This creates a new Finding 2 — Stale-helper guard: confirm File: The comment explains why Finding 3 — Log verbosity for misconfigured plists is bounded but worth a note (no action required) File: This runs inside the 15s-TTL facts cache, so an affected user sees at most one log line per 15 seconds until they repair. The existing comment says "Logs only when the content is invalid," which is accurate. Noting the TTL bound would help future readers not mistake this for a hot-path log. Finding 4 — Acknowledged gap: stale-helper guard has no unit test The PR description calls this out explicitly and the reasoning is sound. Accepted as-is. A What is well done
Summary: One minor cleanup (Finding 1: |
Summary
ServiceHealthChecker.isVHIDDaemonConfiguredCorrectly()(ProgramArguments path +ProcessType=Interactive, the MAL-57 starvation fix) was only consumed by the repair postflight, where it trivially passes because repair just rewrote the plist. Old installs with a pre-fix plist never migrated unless repair ran for some other reason, leaving them vulnerable to the MAL-57 stuck-key autorepeat under CPU load.This wires plist-content validation into the wizard's health model:
ServiceHealthChecker.isVHIDDaemonPlistPresentButMisconfigured()— single-read, presence-gated content check (shared withisVHIDDaemonConfiguredCorrectlyvia one content validator usingPlistGenerator.vhidDaemonPath). A missing plist stays attributed to service health ("not installed"); an existing-but-stale or unparseable plist counts as misconfigured.ComponentStatus.vhidDaemonPlistMisconfigured— new fact, populated bySystemValidatorinside the 15s-TTL component-facts cache (no per-cycle main-thread plist I/O;invalidateCaches()refreshes it after repairs). Deliberately excluded fromhasAllRequired(documented): a stale plist still runs day-to-day.SystemInspectoremits the previously dormant.component(.vhidDaemonMisconfigured)issue — severity.warning(nudges migration without flipping the app to a failed state; wizard routing and the fix button are identifier-based and severity-agnostic), gated on otherwise-healthy services so it doesn't double-alarm with "VHID Services Unhealthy". Auto-fix:.installRequiredRuntimeServices. Existing consumers (KarabinerComponentsStatusEvaluator,WizardRouter, components-page repair) light up unchanged.ActionDeterminerrepair/install plans include the rewrite via a new sharedComponentStatus.vhidRuntimeServicesNeedRepair, so the trigger can't drift between repair and install.PrivilegedOperationsRouter.installRequiredRuntimeServices()now verifies the plist after the helper path and falls back to the in-app install (currentPlistGenerator) so the repair converges.Review gate findings addressed; notably severity was downgraded from
.errorafter verifyingMainAppStateControllercounts.installation+.errorissues as blocking (would have shown an app-wide red/failed state for working installs).Test plan
ServiceHealthCheckerTests: misconfigured check for missing / correct / no-ProcessType / wrong-path / corrupt plists (hermetic viaKEYPATH_LAUNCH_DAEMONS_DIR); fixtures now default toPlistGenerator.vhidDaemonPath; singleton health cache cleared insetUp(fixes a cross-suite flake).WizardPureLogicTests: inspector emits the issue (.installation,.warning, auto-fix, state stays.active); suppressed while services are unhealthy (single alarm, same fix);ActionDeterminerincludes the rewrite when only the plist is stale.Known coverage gap: the stale-helper guard in
PrivilegedOperationsRouter.installRequiredRuntimeServices()(helper reports success → plist still stale → in-app sudo rewrite) has no unit test — it requires mocking a succeedingHelperManageralongside the injectedServiceHealthChecker. The pure-logic detection, issue gating, and action triggers around it are covered; cache freshness after the fallback is handled byMainAppStateController.invalidateValidationCooldown()on wizard close (15s TTL worst case mid-wizard).